// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/ozone/platform/caca/caca_event_source.h"

#include <caca.h>

#include "base/bind.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/ozone/platform/caca/caca_window.h"

namespace ui {

namespace {

    ui::KeyboardCode GetKeyboardCode(const caca_event_t& event)
    {
        // List of special mappings the Caca provides.
        static const ui::KeyboardCode kCacaKeyMap[] = {
            ui::VKEY_UNKNOWN,
            ui::VKEY_A,
            ui::VKEY_B,
            ui::VKEY_C,
            ui::VKEY_D,
            ui::VKEY_E,
            ui::VKEY_F,
            ui::VKEY_G,
            ui::VKEY_BACK,
            ui::VKEY_TAB,
            ui::VKEY_J,
            ui::VKEY_K,
            ui::VKEY_L,
            ui::VKEY_RETURN,
            ui::VKEY_N,
            ui::VKEY_O,
            ui::VKEY_P,
            ui::VKEY_Q,
            ui::VKEY_R,
            ui::VKEY_PAUSE,
            ui::VKEY_T,
            ui::VKEY_U,
            ui::VKEY_V,
            ui::VKEY_W,
            ui::VKEY_X,
            ui::VKEY_Y,
            ui::VKEY_Z,
            ui::VKEY_ESCAPE,
            ui::VKEY_DELETE,
            ui::VKEY_UP,
            ui::VKEY_DOWN,
            ui::VKEY_LEFT,
            ui::VKEY_RIGHT,
            ui::VKEY_INSERT,
            ui::VKEY_HOME,
            ui::VKEY_END,
            ui::VKEY_PRIOR,
            ui::VKEY_NEXT,
            ui::VKEY_F1,
            ui::VKEY_F2,
            ui::VKEY_F3,
            ui::VKEY_F4,
            ui::VKEY_F5,
            ui::VKEY_F6,
            ui::VKEY_F7,
            ui::VKEY_F8,
            ui::VKEY_F9,
            ui::VKEY_F10,
            ui::VKEY_F11,
            ui::VKEY_F12,
        };

        int key_code = caca_get_event_key_ch(&event);
        if (key_code >= 'a' && key_code <= 'z')
            return static_cast<ui::KeyboardCode>(key_code - ('a' - 'A'));
        if (key_code >= '0' && key_code <= 'Z')
            return static_cast<ui::KeyboardCode>(key_code);
        if (static_cast<unsigned int>(key_code) < arraysize(kCacaKeyMap))
            return kCacaKeyMap[key_code];

        return ui::VKEY_UNKNOWN;
    }

    int ModifierFromKey(const caca_event_t& event)
    {
        int key_code = caca_get_event_key_ch(&event);
        if (key_code >= 'A' && key_code <= 'Z')
            return ui::EF_SHIFT_DOWN;
        if ((key_code >= CACA_KEY_CTRL_A && key_code <= CACA_KEY_CTRL_G) || (key_code >= CACA_KEY_CTRL_J && key_code <= CACA_KEY_CTRL_L) || (key_code >= CACA_KEY_CTRL_N && key_code <= CACA_KEY_CTRL_R) || (key_code >= CACA_KEY_CTRL_T && key_code <= CACA_KEY_CTRL_Z))
            return ui::EF_CONTROL_DOWN;

        return ui::EF_NONE;
    }

    int ModifierFromButton(const caca_event_t& event)
    {
        switch (caca_get_event_mouse_button(&event)) {
        case 1:
            return ui::EF_LEFT_MOUSE_BUTTON;
        case 2:
            return ui::EF_RIGHT_MOUSE_BUTTON;
        case 3:
            return ui::EF_MIDDLE_MOUSE_BUTTON;
        }
        return 0;
    }

    // Translate coordinates to bitmap coordinates.
    gfx::PointF TranslateLocation(const gfx::PointF& location, CacaWindow* window)
    {
        gfx::Size physical_size = window->physical_size();
        gfx::Size bitmap_size = window->bitmap_size();
        return gfx::PointF(
            location.x() * bitmap_size.width() / physical_size.width(),
            location.y() * bitmap_size.height() / physical_size.height());
    }

    ui::EventType GetEventTypeFromNative(const caca_event_t& event)
    {
        switch (caca_get_event_type(&event)) {
        case CACA_EVENT_KEY_PRESS:
            return ui::ET_KEY_PRESSED;
        case CACA_EVENT_KEY_RELEASE:
            return ui::ET_KEY_RELEASED;
        case CACA_EVENT_MOUSE_PRESS:
            return ui::ET_MOUSE_PRESSED;
        case CACA_EVENT_MOUSE_RELEASE:
            return ui::ET_MOUSE_RELEASED;
        case CACA_EVENT_MOUSE_MOTION:
            return ui::ET_MOUSE_MOVED;
        default:
            return ui::ET_UNKNOWN;
        }
    }

} // namespace

CacaEventSource::CacaEventSource()
{
}

CacaEventSource::~CacaEventSource()
{
}

void CacaEventSource::TryProcessingEvent(CacaWindow* window)
{
    if (!window->display())
        return;

    caca_event_t event;
    int event_mask = CACA_EVENT_KEY_PRESS | CACA_EVENT_KEY_RELEASE | CACA_EVENT_MOUSE_PRESS | CACA_EVENT_MOUSE_RELEASE | CACA_EVENT_MOUSE_MOTION | CACA_EVENT_RESIZE | CACA_EVENT_QUIT;

    if (!caca_get_event(window->display(), event_mask, &event, 0))
        return;

    switch (caca_get_event_type(&event)) {
    case CACA_EVENT_KEY_PRESS:
    case CACA_EVENT_KEY_RELEASE:
    case CACA_EVENT_MOUSE_PRESS:
    case CACA_EVENT_MOUSE_RELEASE:
    case CACA_EVENT_MOUSE_MOTION:
        OnInputEvent(&event, window);
        break;
    case CACA_EVENT_RESIZE:
        window->OnCacaResize();
        break;
    case CACA_EVENT_QUIT:
        window->OnCacaQuit();
        break;
    default:
        NOTIMPLEMENTED();
    }
}

void CacaEventSource::OnInputEvent(caca_event_t* event, CacaWindow* window)
{
    ui::EventType type = GetEventTypeFromNative(*event);
    bool pressed = type == ui::ET_KEY_PRESSED || type == ui::ET_MOUSE_PRESSED;

    switch (type) {
    case ui::ET_KEY_PRESSED:
    case ui::ET_KEY_RELEASED: {
        if (pressed)
            modifier_flags_ |= ModifierFromKey(*event);
        else
            modifier_flags_ &= ~ModifierFromKey(*event);

        ui::KeyEvent key_event(
            type, GetKeyboardCode(*event), modifier_flags_);
        window->OnCacaEvent(&key_event);
        break;
    }
    case ui::ET_MOUSE_MOVED:
        last_cursor_location_.SetPoint(caca_get_event_mouse_x(event),
            caca_get_event_mouse_y(event));
        // Update cursor location.
        caca_gotoxy(caca_get_canvas(window->display()),
            last_cursor_location_.x(),
            last_cursor_location_.y());

    // fallthrough
    case ui::ET_MOUSE_PRESSED:
    case ui::ET_MOUSE_RELEASED: {
        int flags = 0;
        int changed_flags = 0;
        if (type != ui::ET_MOUSE_MOVED) {
            if (pressed) {
                changed_flags = ModifierFromButton(*event);
                modifier_flags_ |= changed_flags;
            } else {
                modifier_flags_ &= ~changed_flags;
            }
            // On release the button pressed is removed from |modifier_flags_|,
            // but sending the event needs it set.
            flags = modifier_flags_ | changed_flags;
        }
        gfx::PointF location = TranslateLocation(last_cursor_location_, window);
        ui::MouseEvent mouse_event(type, gfx::Point(), gfx::Point(),
            EventTimeForNow(), flags, changed_flags);
        mouse_event.set_location_f(location);
        mouse_event.set_root_location_f(location);
        window->OnCacaEvent(&mouse_event);
        break;
    }
    default:
        NOTIMPLEMENTED();
        break;
    }
}

} // namespace ui
